﻿//#define Testing
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Threading;
using NextDart;

namespace NextDart
{
	public class SimpleParser
	{
		#region public Enums

		public enum Directions
		{
			North, South
		}

		#endregion

		#region private members and constants

		private string ListOfTimes { get; set; }

		#endregion private members and constants

		#region public members

		public DateTime QueryTime { get; private set; }
		public string Description { get; set; }
		public List<DartDetails> NextDarts { get; private set; }

		#endregion public members

		public SimpleParser()
		{
			QueryTime = DateTime.Now;
			NextDarts = new List<DartDetails>();
		}

        public void Parse()
		{
            string page = FetchPage(NextDartConfiguration.Instance.Link);
			ProcessHeader(page, true);

			List<List<string>> trains = ParseList();
			ProcessList(trains);
		}

		private string FetchPage(string url)
		{
			// prepare the web page we will be asking for
#if Testing
            using (Stream resStream = new FileStream(@"..\..\SamplePage.html", FileMode.Open, FileAccess.Read))
            {
#else
			HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url.ToString());
			request.UserAgent = "User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2)";
			using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            using (Stream resStream = response.GetResponseStream())
            {
#endif //Testing

                // used to store the entire page
                StringBuilder content = new StringBuilder();
                // used on each read operation
                byte[] buffer = new byte[8192];
                string tempString = null;
                int count = 0;

                do
                {
                    // fill the buffer with data
                    count = resStream.Read(buffer, 0, buffer.Length);

                    // check that there was data
                    if (count == 0) break;

                    // translate from bytes to ASCII text
                    tempString = Encoding.ASCII.GetString(buffer, 0, count);

                    // continue building the string
                    content.Append(tempString);
                }
                while (count > 0); // any more data to read?

                // return the page
                return content.ToString();
            }
		}

		private void ProcessHeader(string page, bool getPageNumbers)
		{
            int startOfList = page.IndexOf("Realtime Results for");
			if (startOfList == -1) 
            {
                ListOfTimes = "";
                return;
            }

            int endOfList = page.Length;
			ListOfTimes = page.Substring(startOfList, endOfList - startOfList);

            GetCurrentTime();
            GetDescription();

            int northbound = ListOfTimes.IndexOf("Journey Northbound");
            int southbound = ListOfTimes.IndexOf("Journey Southbound", northbound);
            if (NextDartConfiguration.Instance.NorthBound)
            {
                ListOfTimes = ListOfTimes.Substring(northbound, southbound - northbound);
            }
            else
            {
                ListOfTimes = ListOfTimes.Substring(southbound);
            }
        }

		private void GetCurrentTime()
		{
			const string startMarker = "- ";
			int startOfList = ListOfTimes.IndexOf(startMarker);
			if (startOfList == -1) return;
			startOfList += startMarker.Length;

			int endOfList = ListOfTimes.IndexOf("</b>", startOfList);
			if (endOfList == -1) return;

			string time = ListOfTimes.Substring(startOfList, endOfList - startOfList);
			ListOfTimes = ListOfTimes.Substring(endOfList);

			QueryTime = GetDateTimeFromString(time);
		}

		private void GetDescription()
		{
            Description = string.Format("Darts from {0} going {1}", NextDartConfiguration.Instance.Station,
                (NextDartConfiguration.Instance.NorthBound) ? "North" : "South");
		}

		private List<List<string>> ParseList()
		{
			const string startTag = "<tr";
			const string endTag = "<tr";

			int start = 0;
			int end = 0;

			List<List<string>> trains = new List<List<string>>();

			while ((start != -1) && (end != -1))
			{
				start = ListOfTimes.IndexOf(startTag, end, StringComparison.CurrentCultureIgnoreCase);
				if (start == -1) break;

				start += startTag.Length;
				end = ListOfTimes.IndexOf(endTag, start, StringComparison.CurrentCultureIgnoreCase);
				end = (end != -1) ? end : ListOfTimes.Length;

				string train = ListOfTimes.Substring(start, end - start);
				List<string> columns = ParseTrain(train);
				trains.Add(columns);
			}

			return trains;
		}

		private List<string> ParseTrain(string tender)
		{
			const string startTag = "<td";
			const string endTag = "</td>";

			int start = 0;
			int end = 0;

			List<string> columns = new List<string>(7);

			// first is a special case since it's a property of the <tr>
			end = tender.IndexOf(startTag, start, StringComparison.CurrentCultureIgnoreCase);
			if (end == -1) return null;
			string column = tender.Substring(start, end - start);
			columns.Add(column);

			while ((start != -1) && (end != -1))
			{
				start = tender.IndexOf(startTag, end, StringComparison.CurrentCultureIgnoreCase);
				if (start == -1) break;

				start += startTag.Length;
				end = tender.IndexOf(endTag, start, StringComparison.CurrentCultureIgnoreCase);
				end = (end != -1) ? end : tender.Length;

				column = tender.Substring(start, end - start);
				columns.Add(column);
			}

			ParseColumns(columns);
			return columns;
		}

		private void ParseColumns(List<string> columns)
		{
			if (columns.Count != 7) return;

			// drop the first one
			columns.RemoveAt(0);

			for (int i = 0; i  < columns.Count; ++i)
			{
				const string endIdTag = ">";
				int endId = columns[i].IndexOf(endIdTag) + 1;
				columns[i] = columns[i].Substring(endId).Trim();
			}
		}

		private void ProcessList(List<List<string>> trains)
		{
            if (trains.Count == 0) return;

			NextDarts.Clear();

			foreach (var train in trains)
			{
				ProcessColumns(train);
			}
		}

		private void ProcessColumns(List<string> columns)
		{
			if ((columns == null) || (columns.Count < 6))return;

            string journey = ProcessJourney(columns[0]);
			string scheduled = columns[2];
            string eta = ProcessETA(columns[3]);
            string dueIn = columns[4];
            string currentLocation = ProcessCurrentLocation(columns[5]);

			DartDetails details = new DartDetails
			{
                Journey = journey,
                ETA = GetDateTimeFromString(eta),
                Scheduled = GetDateTimeFromString(scheduled),
                DueIn = GetMinutesFromString(dueIn),
                CurrentLocation = currentLocation
			};

            if (details.DueIn < NextDartConfiguration.Instance.DiscardInterval) return;

			NextDarts.Add(details);
		}

        private string ProcessJourney(string input)
        {
            input = input.Replace("&nbsp;", " ");

            int startOf = input.LastIndexOf(" - ");
            if (startOf != -1) 
                input = input.Substring(startOf + 3);

            int endOf = input.LastIndexOf("(<");
            if (endOf != -1)
                input = input.Substring(0, endOf);

            return input;
        }

        private string ProcessCurrentLocation(string input)
        {
            input = input.Replace("&nbsp;", " ");
            return input;
        }

        private string ProcessETA(string input)
        {
            input = input.ToLower();
            if (!input.Contains("font")) return input;

            int startOf = input.IndexOf('>');
            input = input.Substring(startOf + 1);
            int endOf = input.IndexOf('<');
            input = input.Substring(0, endOf);
            return input;
        }

		private DateTime GetDateTimeFromString(string time)
		{
			string[] parts = time.Split(':');
			return DateTime.Now.Date.AddHours(int.Parse(parts[0])).AddMinutes(int.Parse(parts[1]));
		}

		private int GetMinutesFromString(string time)
		{
			string[] parts = time.Split(' ');
			return int.Parse(parts[0]);
		}
	}
}
